#include <iostream>
#include <random>
#include <thread>
#include <chrono>

#include <boost/asio.hpp>
#include <boost/asio/ssl.hpp>
#include <boost/beast/version.hpp>
#include <boost/date_time/posix_time/posix_time.hpp>

#include "coin2/base/request/session.h"
#include "coin2/base/request/root_certificates.h"

namespace ssl     = boost::asio::ssl;
namespace request = coin2::base::request;
namespace http    = boost::beast::http;

class HttpClient : public std::enable_shared_from_this<HttpClient> {
public:
  HttpClient(boost::asio::io_context& io_ctx,
          const std::string& host,
          int port,
          const std::string& local_addr = "") 
    : io_ctx_{io_ctx}, 
      timer_{io_ctx_},
      host_{host},
      port_{port},
      local_addr_{local_addr},
      unif_{0, 9} {

    ssl_ctx_ = request::GetDefaultSslContext();
  }

  void Start() {
    session1_ = std::make_shared<request::Session>(&io_ctx_, ssl_ctx_, host_, port_, local_addr_);
    session2_ = std::make_shared<request::Session>(&io_ctx_, ssl_ctx_, host_, port_);
    timer_.expires_from_now(boost::posix_time::milliseconds(interval_));
    timer_.async_wait([this, self = shared_from_this()](const boost::system::error_code& ec) {
        SendRequest();
    });
  }

  void Interval(int interval) {
    interval_ = interval;
  }

private:
  void SendRequest() {
    request::Session::RequestType req;
    req.version(11);
    req.method(http::verb::get);
    req.keep_alive(true);
    req.set(http::field::user_agent, BOOST_BEAST_VERSION_STRING);

    const std::string target1 = "/?session=1&index=";
    const std::string target2 = "/?session=2&index=";
    const std::string interval = std::string("&interval=") + std::to_string(interval_);

    auto index = std::to_string(index_);
    auto rand  = unif_(engine_);
    if (rand % 2 == 0) {
      req.target(target1 + index + interval);
      req.prepare_payload();
      session1_->AsyncRequest(req, [](boost::system::error_code, const request::SessionResponse* res) {
          if (res != nullptr && res->raw_response != nullptr) {
            std::cout << res->raw_response->body() << "\n";
          }
      });
      req.target(target2 + index + interval);
      req.prepare_payload();
      session2_->AsyncRequest(req, [](boost::system::error_code, const request::SessionResponse* res) {
          if (res != nullptr && res->raw_response != nullptr) {
            std::cout << res->raw_response->body() << "\n";
          }
      });
    } else {
      req.target(target2 + index + interval);
      req.prepare_payload();
      session2_->AsyncRequest(req, [](boost::system::error_code, const request::SessionResponse* res) {
          if (res != nullptr && res->raw_response != nullptr) {
            std::cout << res->raw_response->body() << "\n";
          }
      });
      req.target(target1 + index + interval);
      req.prepare_payload();
      session1_->AsyncRequest(req, [](boost::system::error_code, const request::SessionResponse* res) {
          if (res != nullptr && res->raw_response != nullptr) {
            std::cout << res->raw_response->body() << "\n";
          }
      });
    }
    index_++;

    if (index_ == 1000) {
      return;
    }

    timer_.expires_from_now(boost::posix_time::milliseconds(interval_));
    timer_.async_wait([this, self = shared_from_this()](const boost::system::error_code& ec) {
        SendRequest();
    });
  }

private:
  boost::asio::io_context&                io_ctx_;
  boost::asio::deadline_timer             timer_;
  const std::string                       host_;
  const int                               port_;
  std::string                             local_addr_;
  ssl::context*                           ssl_ctx_;
  std::shared_ptr<request::Session>       session1_;
  std::shared_ptr<request::Session>       session2_;
  std::uniform_int_distribution<unsigned> unif_;
  std::default_random_engine              engine_;
  int                                     index_        = 0;
  int                                     interval_     = 50 /* milliseconds */;
};

int main(int argc, char **argv) {

  if (argc < 3) {
    std::cout << "Usage: client <host> <port> [local_addr] [interval]\n";
    return 1;
  }

  //auto host       = "10.21.1.29";
  //auto port       = 8080;
  //auto local_addr = "10.81.1.122";
  std::string host       = std::string(argv[1]);
  int port               = atoi(argv[2]);
  std::string local_addr = "";
  int interval           = 40 /* milliseconds */ ;
  if (argc > 3) {
    local_addr = std::string(argv[3]);
  }
  if (argc > 4) {
    interval = atoi(argv[4]);
  }

  boost::asio::io_context io_ctx;
  auto mc = std::make_shared<HttpClient>(io_ctx, host, port, local_addr);
  mc->Interval(interval);
  mc->Start();
  io_ctx.run();

  return 0;
}
